#include "Obj.h"
#include "Utils.h"
#include "Input.h"
#include "Gui.h"
#include "Core.h"

void Obj::Update(float dt)
{
	Vel = Vel - Vel * Friction * dt;
	Pos = Pos + Vel * dt;
	Life += dt;

	for (eastl::map<int, double>::iterator it=Timers.begin(); it!=Timers.end(); ++it)
	{
		if (it->second > 0.0)
		{
			it->second -= dt;
			if (it->second <= 0.0)
			{
				it->second = 0.0;
				Event(EV_TIMER, Prop(double(it->first)));
			}
		}
	}
}

double Obj::GetTimer(int timer)
{
	eastl::map<int, double>::iterator it = Timers.find(timer);
	if (it != Timers.end())
		return it->second;
	return 0.0;
}

void Obj::SetTimer(int timer, double time)
{
	Timers[timer] = time;
}

void Obj::Render( const Vec& offset )
{
	if (Img)
		DrawPic(Img, Pos.X+offset.X, Pos.Y+offset.Y);
}

void Obj::Collide( Obj& other, bool first )
{

}

void Obj::Event(int event, const Prop& arg)
{
}

///************************************************************************/
/// World
///************************************************************************/

World::World() : Paused(false), DoCollisions(true), DoMouseEvents(true), mMouseOverObject(0), mMouseDownObject(0)
{

}

World::~World()
{
	for (size_t i=0; i<Objects.size(); i++)
		delete Objects[i];
}

bool DeleteObj(Obj* o) 
{ 
	if (o->Dead)
	{
		delete o;
		return true;
	}
	return false;
}

bool Collides(Obj* o1, Obj* o2)
{
	assert(o1 && o2);
	if (o1 == o2) 
		return false;
	else if (o1->Circular && o2->Circular)
		return o1->IntersectsRadius(*o2);
	else if (!o1->Circular && !o2->Circular)
		return o1->IntersectsSquare(*o2);
	assert(0 && "Circle <-> Rectangle collision not supported");
	return false;
}

void World::Update(float dt)
{
	if (!Paused)
	{
		/// Collisions
		if (DoCollisions)
		{
			for (size_t n=0; n<Objects.size(); n++)
			{
				Obj* o1 = Objects[n];
				if (o1->Dead || !o1->CollisionGroup) continue;
				uint64 t = (uint64)o1->CollisionGroup;
				for (size_t i=n+1; i<Objects.size(); i++)
				{
					Obj* o2 = Objects[i];
					if (o2->Dead || !o2->CollisionGroup) continue;

					uint64 t2 = (uint64)o2->CollisionGroup;
					uint64 c1 = (t<<32LL) | t2;
					uint64 c2 = (t) | (t2<<32LL);

					if (!Collides(o1, o2))
						continue;
					
					size_t c1c = Collisions.count(c1);
					size_t c2c = Collisions.count(c2);

					if (c1c)
						o1->Collide(*o2, true);
					if (c2c)
						o2->Collide(*o1, false);
				}
			}
		}

		/// Mouse events
		if (DoMouseEvents)
			DoMouse();

		/// Update
		for (size_t i=0; i<Objects.size(); i++)
			if (!Objects[i]->Dead)
				Objects[i]->Update(dt);

		Objects.erase(eastl::remove_if(Objects.begin(), Objects.end(), DeleteObj), Objects.end());
	}
}

size_t World::CountObjects(int of_type)
{
	size_t c = 0;
	for (size_t i=0; i<Objects.size(); i++)
	{
		if (!Objects[i]->Dead && (!of_type || Objects[i]->Type()==of_type))
			c++;
	}
	return c;
}

void World::DoMouse()
{
	Vec mousepos = GetMousePos();
	Obj* obj = 0;

	if (!GetGUIHotItem())
		obj = GetObjectAt(mousepos);
	
	Vec p = mousepos-RenderOffset;
	if (mMouseOverObject != obj)
	{
		if (mMouseOverObject)
			mMouseOverObject->Event(MEV_OUT, Vec());
		if (obj)
			obj->Event(MEV_OVER, obj->Pos-p);
		mMouseOverObject = obj;
	}

	if (WasPressed(MOUSE_LEFT))
	{
		if (obj)
		{
			obj->Event(MEV_DOWN, obj->Pos-p);
			mMouseDownObject = obj;
		}
	} else if (WasReleased(MOUSE_LEFT))
	{
		if (mMouseDownObject)
			mMouseDownObject->Event(MEV_UP, Vec());
		if (mMouseOverObject && mMouseDownObject == mMouseOverObject)
		{
			mMouseDownObject->Event(MEV_CLICK, p-obj->Pos);
		}
		mMouseDownObject = 0;
	}
}

void World::Render()
{
	for (size_t i=0; i<Objects.size(); i++)
		if (!Objects[i]->Dead && ((1U<<CurrentRenderPass) & Objects[i]->RenderInPasses))
			Objects[i]->Render(RenderOffset);
}

Obj* World::AddToWorld( Obj* obj )
{
	Objects.push_back(obj);
	obj->Wrld = this;
	obj->Event(WRL_ADDED, Prop());
	return obj;
}

Obj* World::AddToWorld( Obj* obj, const Vec& pos )
{
	obj->Pos = pos;
	return AddToWorld(obj);
}

Obj* World::RemoveFromWorld( Obj* obj )
{
	FindAndErase(Objects, obj);
	obj->Event(WRL_REMOVED, Prop());
	if (obj->Wrld == this) obj->Wrld = 0;
	return obj;
}

bool World::InWorld( const Obj* obj )
{
	Obj* o = FindOr(Objects, (Obj*)obj, (Obj*)0);
	if (!o) return false;
	return !o->Dead;
}

Obj* World::GetObjectAt(const Vec& pos)
{
  Vec p = pos-RenderOffset;
  for (int i=Objects.size()-1; i>=0; --i)
		if (!Objects[i]->Dead)
      if (Objects[i]->IntersectPoint(p)) return Objects[i];
  return 0;
}

void World::BroadcastEvent(int event, const Prop& arg, int type)
{
	for (size_t i=0; i<Objects.size(); i++)
		if (!Objects[i]->Dead)
      if (!type || type == Objects[i]->Type()) Objects[i]->Event(event, arg);
}

void World::GetObjectsAt( const Vec& pos, eastl::vector<Obj*>& objects )
{
	Vec p = pos-RenderOffset;
	for (size_t i=0; i<Objects.size(); i++)
		if (!Objects[i]->Dead)
			if (Objects[i]->IntersectPoint(p)) objects.push_back(Objects[i]);
}

void World::AddCollision( uint type1, uint type2 )
{
	uint64 col = (uint64(type1)<<32LL) | (uint64(type2));
	Collisions.insert(col);
}

void World::RemoveCollision( uint type1, uint type2 )
{
	uint64 col = (uint64(type1)<<32LL) | (uint64(type2));
	Collisions.erase(col);
}

void World::KillAll()
{
	for (size_t i=0; i<Objects.size(); i++)
		delete Objects[i];
	Objects.clear();
}